<template>
  <svg :width="width" :height="height" class="stage" ref="stage">
    <yc-line v-for="item in data.lines" :key="'line' + item.id" :option="opt.line" :data="item" ref="lines"></yc-line>
    <yc-node v-for="item in data.nodes" :key="'node' + item.id" :option="opt.node" :data="item" ref="nodes"></yc-node>
    <yc-frame :option="opt.frame" ref="frame"></yc-frame>
  </svg>
</template>

<script>
import YcLine from '../line'
import YcNode from '../node'
import YcFrame from '../frame'

export default {
  components: {
    YcLine,
    YcNode,
    YcFrame
  },
  props: {
    width: {
      default: '100%'
    },
    height: {
      default: undefined
    },
    option: {}
  },
  data () {
    return {
      data: { lines: [], nodes: [] },
      frame: { x: 0, y: 0, width: 100, height: 100 }, // 选择框数据
      maxId: 0,
      histories: [], // 历史记录
      historyIndex: -1 // 移动历史记录的序号
    }
  },
  methods: {
    // click () {
    //   console.log(this.histories, this.historyIndex)
    //   this.cancelSelect()
    // },
    /**
     * 自增生成一个id
     * @return {Integer} id
     * @author Jiangzhiwei
     * @time   2021-11-25T23:16:34+0800
     */
    generateId () {
      this.maxId++
      return this.maxId
    },
    /**
     * 获取node组件，没找到则返回null
     * @param    {Integer} id 唯一标识
     * @return   {Object} node组件
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:15:39+0800
     */
    getNodeComp (id) {
      return this.getNodeComps().find(item => item.data.id === id)
    },
    /**
     * 获取所有node组件
     * @return   {Array} node组件数组
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:16:37+0800
     */
    getNodeComps () {
      return this.$refs.nodes || []
    },
    /**
     * 根据id获取节点数据
     * @param  {Integer} id 节点id
     * @return {Object} 节点数据
     * @author Jiangzhiwei
     * @time   2021-11-27T00:17:13+0800
     */
    getNode (id) {
      return this.data.nodes.find(item => item.id === id)
    },
    /**
     * 新增节点
     * @param  {Object} nodeData 基本节点数据 { x, y, text, color, bgColor, hasHead, hasTail, isSelected }
     * @return {Integer} 实际节点数据
     * @author Jiangzhiwei
     * @time   2021-11-25T23:11:32+0800
     */
    addNode (nodeData) {
      const id = this.generateId()
      nodeData.id = id
      const data = Object.assign({
        x: 0,
        y: 0,
        text: '节点',
        color: 'black',
        bgColor: '#409eff',
        isSelected: false
      }, nodeData)
      this.data.nodes.push(data)
      return data
    },
    /**
     * 根据id删除节点
     * @param  {Integer} nodeId 节点id
     * @return {Boolean} 是否删除了一个节点
     * @author Jiangzhiwei
     * @time   2021-11-25T23:17:00+0800
     */
    removeNode (nodeId) {
      const index = this.data.nodes.findIndex(item => item.id === nodeId)
      if (index !== -1) {
        this.data.nodes.splice(index, 1)
        return true
      }
      return false
    },
    /**
     * 获取line组件，没找到则返回null
     * @param    {Integer} id 唯一标识
     * @return   {Object} line组件
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:16:56+0800
     */
    getLineComp (id) {
      return this.getLineComps().find(item => item.data.id === id)
    },
    /**
     * 获取所有line组件
     * @return   {Array} line组件数组
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:17:19+0800
     */
    getLineComps () {
      return this.$refs.lines || []
    },
    /**
     * 获得连线数据
     * @param    {Integer} id 唯一标识
     * @return   {Object} 连线数据
     * @Author   Jiangzhiwei
     * @datetime 2021-10-22T14:12:26+0800
     */
    getLine (id) {
      return this.data.lines.find(item => item.id === id)
    },
    /**
     * 新增连线
     * @param  {Object} lineData 基本连线数据 { head, tail, headNodeId, tailNodeId, isSelected }
     * @return {Object} 实际连线数据
     * @author Jiangzhiwei
     * @time   2021-11-25T23:17:58+0800
     */
    addLine (lineData) {
      const id = this.generateId()
      lineData.id = id
      const data = Object.assign({
        head: { x: 0, y: 0 },
        tail: { x: 0, y: 0 },
        headNodeId: -1,
        tailNodeId: -1,
        state: 0,
        isSelected: false
      }, lineData)
      this.data.lines.push(data)
      // console.log(data)
      return data
    },
    /**
     * 根据id删除连线
     * @param  {Integer} lineId 连线id
     * @return {Boolean} 是否删除了一条连线
     * @author Jiangzhiwei
     * @time   2021-11-25T23:18:20+0800
     */
    removeLine (lineId) {
      const index = this.data.lines.findIndex(item => item.id === lineId)
      if (index !== -1) {
        this.data.lines.splice(index, 1)
        return true
      }
      return false
    },
    /**
     * 新增历史记录
     * @author Jiangzhiwei
     * @time   2021-11-25T00:25:41+0800
     */
    addHistory () {
      this.historyIndex++
      this.histories.length = this.historyIndex
      this.histories[this.historyIndex] = JSON.stringify(this.data)
      this.$emit('history-change', this.histories.length, this.historyIndex + 1)
    },
    /**
     * 重做
     * @return {Boolean} 是否重做成功
     * @author Jiangzhiwei
     * @time   2021-11-25T00:25:48+0800
     */
    forwardHistory () {
      if (this.histories[this.historyIndex + 1]) {
        this.historyIndex++
        this.recoverHistory()
        // console.log('重做', this.historyIndex)
        return true
      }
      return false
    },
    /**
     * 撤销
     * @return {Boolean} 是否撤销成功
     * @author Jiangzhiwei
     * @time   2021-11-25T00:26:38+0800
     */
    backwardHistory () {
      if (this.historyIndex > 0) {
        this.historyIndex--
        this.recoverHistory()
        // console.log('撤销', this.historyIndex)
        return true
      }
      return false
    },
    /**
     * 恢复数据
     * @author Jiangzhiwei
     * @time   2021-11-25T00:27:01+0800
     */
    recoverHistory () {
      const dataString = this.histories[this.historyIndex]
      const data = JSON.parse(dataString)
      this.loadData(data)
      this.$emit('history-change', this.histories.length, this.historyIndex + 1)
      // console.log(dataString)
    },
    /**
     * 加载数据
     * @param  {Object} data 所有数据，包括节点与连线
     * @author Jiangzhiwei
     * @time   2021-11-25T23:30:27+0800
     */
    loadData (data = {}) {
      if (!data.lines) {
        this.$set(this.data, 'lines', [])
      } else {
        this.data.lines = data.lines
      }
      if (!data.nodes) {
        this.$set(this.data, 'nodes', [])
      } else {
        this.data.nodes = data.nodes
      }
      // 更新最大id
      this.updateMaxId()
      if (this.historyIndex === -1) { // 初次加载的时候，保存历史记录
        this.addHistory()
      }
    },
    /**
     * 更新最大id
     * @author Jiangzhiwei
     * @time   2021-11-25T23:30:59+0800
     */
    updateMaxId () {
      let maxId = 0
      this.data.lines.forEach(item => {
        if (item.id > maxId) {
          maxId = item.id
        }
      })
      this.data.nodes.forEach(item => {
        if (item.id > maxId) {
          maxId = item.id
        }
      })
      this.maxId = maxId
    },
    /**
     * 获取点在画布中的位置
     * @param  {Object} event 事件
     * @return {Object} 位置
     * @author Jiangzhiwei
     * @time   2021-11-01T23:46:28+0800
     */
    getPoint (event) {
      const rect = this.$refs.stage.getBoundingClientRect()
      const x = event.clientX - rect.left
      const y = event.clientY - rect.top
      return { x, y }
    },
    /**
     * 单选
     * @param  {Object} comp 组件
     * @param  {Boolean} pressShift 是否按下Shift键
     * @return {Boolean} 是否有组件选中状态发生改变
     * @author Jiangzhiwei
     * @time   2021-11-27T17:36:47+0800
     */
    singleSelect (comp, pressShift) {
      // console.log('singleSelect')
      let hasChanged
      if (pressShift) { // 如果按下shift
        if (comp.isSelected()) { // 已选中
          comp.setSelected(false) // 取消选中
        } else { // 未选中
          comp.setSelected() // 选中
        }
        hasChanged = true
      } else { // 没有按shift键
        hasChanged = this.cancelSelect(comp) // 全部取消选中
        if (!comp.isSelected()) { // 未选中
          comp.setSelected() // 选中
          hasChanged = true
        }
        return hasChanged
      }
    },
    /**
     * 多选（框选）
     * @param  {Object || Array} comps 组件
     * @param  {Boolean} pressShift 是否按下shift键
     * @return {Boolean} 是否有组件选中状态发生改变
     * @author Jiangzhiwei
     * @time   2021-11-28T00:41:31+0800
     */
    multiSelect (comps, pressShift) {
      // console.log('multiSelect')
      let hasChanged = false
      if (pressShift) { // 如果按下shift，则将未选中的选中，已选中的不作处理
        comps.forEach(item => {
          if (!item.isSelected()) { // 未选中
            item.setSelected() // 选中
            hasChanged = true
          }
        })
      } else { // 未按下shift
        const selectedComps = this.getSelectedComps().sort((a, b) => a.data.id - b.data.id)
        const needSelectComps = comps.sort((a, b) => a.data.id - b.data.id)
        if (selectedComps.map(item => item.data.id).join(',') !==
          needSelectComps.map(item => item.data.id).join(',')) { // 拼接逗号后字符串不相同，表示选中不一致
          this.cancelSelect() // 全部取消
          // 设置选中
          needSelectComps.forEach(item => {
            item.setSelected()
          })
          hasChanged = true
        }
      }
      if (hasChanged && this.opt.recordSelected) { // 如果状态改变 且 需要记录
        this.addHistory()
      }
    },
    /**
     * 除某组件外，其他组件全部取消选中
     * @param  {Object} comp 组件
     * @return {Boolean} 是否有节点的选中状态发生变化
     * @author Jiangzhiwei
     * @time   2021-11-27T19:47:07+0800
     */
    cancelSelect (comp) {
      // console.log('cancelSelect')
      let id // 组件id
      let type // 组件类型
      if (comp) {
        id = comp.data.id
        type = comp.type
      } else {
        id = -1
        type = ''
      }
      let hasChanged = false
      // 节点取消选中
      const nodeComps = this.getNodeComps()
      nodeComps.forEach(item => {
        if (item.data.id !== id) { // 不是排除的组件
          if (item.isSelected()) {
            item.setSelected(false)
            hasChanged = true
          }
        }
      })
      // 连线取消选中
      const lineComps = this.getLineComps()
      if (type === 'node') { // 如果排除的是节点
        lineComps.forEach(item => {
          if (item.data.headNodeId !== id && item.data.tailNodeId !== id) { // 没有与该节点连接
            if (item.isSelected()) {
              item.setSelected(false)
              hasChanged = true
            }
          }
        })
      } else {
        lineComps.forEach(item => {
          // console.log(item.data.id, id)
          if (item.data.id !== id) { // 不是排除的组件
            if (item.data.isSelected) {
              item.data.isSelected = false
              hasChanged = true
            }
          }
        })
      }
      return hasChanged
    },
    /**
     * 获取所有选中的组件
     * @return {Array} 组件数组
     * @author Jiangzhiwei
     * @time   2021-11-27T19:40:03+0800
     */
    getSelectedComps () {
      const nodeComps = this.getNodeComps().filter(item => item.data.isSelected)
      const lineComps = this.getLineComps().filter(item => item.data.isSelected)
      nodeComps.push(...lineComps)
      return nodeComps
    },
    /**
     * 获取选中的节点
     * @param  {Object} comp 排除的节点
     * @return {Array} 选中的节点数组
     * @author Jiangzhiwei
     * @time   2021-11-27T23:30:51+0800
     */
    getSelectedNodes (comp) {
      if (comp) {
        return this.getNodeComps().filter(item => item.data.isSelected && item.data.id !== comp.data.id)
      } else {
        return this.getNodeComps().filter(item => item.data.isSelected)
      }
    },
    /**
     * 移除组件
     * @param  {Array} comps 组件数组
     * @author Jiangzhiwei
     * @time   2021-11-27T19:35:06+0800
     */
    removeComps (comps) {
      comps.forEach(item => {
        item.remove()
      })
      this.addHistory()
    },
    /**
     * 删除选中的组件
     * @author Jiangzhiwei
     * @time   2021-11-27T19:43:16+0800
     */
    removeSelectedComps () {
      const comps = this.getSelectedComps()
      this.removeComps(comps)
    },
    /**
     * 快捷键 按键抬起
     * @param  {Object} event 按键事件
     * @author Jiangzhiwei
     * @time   2021-11-27T19:45:05+0800
     */
    keyup (event) {
      const key = event.key
      if (key === 'Delete' || key === 'Backspace') { // 删除
        this.removeSelectedComps() // 删除选中的组件
      } else if (key === 'z' && event.ctrlKey) { // ctrl + z 撤销
        this.backwardHistory()
      } else if (key === 'y' && event.ctrlKey) { // ctrl + y 前进
        this.forwardHistory()
      }
    },
    /**
     * 按下准备框选
     * @param  {Object} event 事件
     * @author Jiangzhiwei
     * @time   2021-11-01T23:45:18+0800
     */
    mousedown (event) {
      // 判断是否在舞台中
      // console.log(this.isEnterStage(event))
      if (this.isEnterStage(event)) {
        const point = this.getPoint(event)
        this.$refs.frame.setStartPoint(point)
      }
    },
    /**
     * 框选移动中
     * @param  {Object} event 事件
     * @author Jiangzhiwei
     * @time   2021-11-01T23:45:41+0800
     */
    mousemove (event) {
      if (this.$refs.frame.isStart) {
        const point = this.getPoint(event)
        this.$refs.frame.setFinishPoint(point) // isFinished = true
      }
    },
    /**
     * 框选结束
     * @param  {Object} event 事件
     * @author Jiangzhiwei
     * @time   2021-11-01T23:46:03+0800
     */
    mouseup (event) {
      if (this.$refs.frame.isFinished) { // 表示进行了框选
        const comps = this.$refs.frame.getSelected()
        // console.log(comps)
        this.multiSelect(comps, event.shiftKey)
      } else { // 没有框选
        // 判断是否在舞台中
        if (this.isEnterStage(event)) { // 在舞台中
          const hasChanged = this.cancelSelect()
          if (hasChanged && this.opt.recordSelected) { // 如果状态改变 且 需要记录
            this.addHistory()
          }
        }
      }
      if (this.$refs.frame.isStart) {
        this.$refs.frame.setOver()
      }
      // console.log(this.historyIndex, this.histories)
    },
    /**
     * 当前事件位置是否在舞台中
     * @param  {Object} event 事件
     * @return {Boolean} 是否在舞台中
     * @author Jiangzhiwei
     * @time   2021-11-28T20:13:53+0800
     */
    isEnterStage (event) {
      const rect = this.$refs.stage.getBoundingClientRect()
      return event.clientX > rect.left && event.clientX < rect.right && event.clientY > rect.top && event.clientY < rect.bottom
    }
  },
  computed: {
    opt () {
      const option = {
        node: {},
        line: {},
        frame: {},
        recordSelected: true // 是否记录选中状态改变
      }
      return Object.assign(option, this.option)
    }
  },
  watch: {
    data (val) {
      this.stageData = val
    }
  },
  mounted () {
    document.addEventListener('keyup', this.keyup)
    document.addEventListener('mousedown', this.mousedown)
    document.addEventListener('mousemove', this.mousemove)
    document.addEventListener('mouseup', this.mouseup)
    this.$emit('onload', this)
  },
  destroyed () {
    document.removeEventListener('keyup', this.keyup)
    document.removeEventListener('mousedown', this.mousedown)
    document.removeEventListener('mousemove', this.mousemove)
    document.removeEventListener('mouseup', this.mouseup)
  }
}
</script>
